iT邦幫忙

2026 iThome 鐵人賽

DAY 29
0
Vibe Coding

夢幻甜品師闖工程世界:Vibe Coding vs 專業開發的 0→1 冒險攻略系列 第 29

Day29|Index 加了,Database 真的有在用嗎?從 EXPLAIN ANALYZE 看懂 Query 到底怎麼跑

  • 分享至 

  • xImage
  •  

昨天,我們替這支 Query 提出了一個 Index Design:

SELECT *
FROM orders
WHERE user_id = 123
  AND status = 'completed'
ORDER BY created_at DESC;
CREATE INDEX idx_orders_user_status
ON orders(user_id, status);

但 Index 加上去之後,我們要怎麼知道它是不是真的有幫上忙呢?

今天就直接打開 EXPLAIN ANALYZE 看。


EXPLAIN ANALYZE 是什麼?

先從它到底在做什麼開始。

EXPLAIN ANALYZE 是 PostgreSQL 用來顯示 Query Execution Plan(查詢執行計畫),並實際執行這支 Query、補上真實執行資料的工具。

例如昨天的 Query,只要在前面加上:

EXPLAIN ANALYZE
SELECT *
FROM orders
WHERE user_id = 123
  AND status = 'completed'
ORDER BY created_at DESC;

原本我們只會拿到查詢結果。

加上 EXPLAIN ANALYZE 後,看到的則會變成:

Query 到底走哪一條路?
用了哪個 Index?
Planner 原本估計要處理多少資料?
實際又處理了多少?
實際花了多少時間?

也就是把 Database 執行這支 Query 的過程攤開來看。EXPLAIN 可以看到 Planner 產生的 Execution Plan;加上 ANALYZE 後,Query 會真的被執行,並補上實際時間與 Row 數。

這裡也要先分清楚兩個很像的指令:

EXPLAIN
→ 看 Database「打算怎麼跑」

EXPLAIN ANALYZE
→ 真的跑一次,再把實際結果一起顯示

所以如果只是:

EXPLAIN
SELECT * FROM orders;

看到的是 Planner 的估算。

但:

EXPLAIN ANALYZE
SELECT * FROM orders;

除了原本的估算,還會多出真正執行後的 actual timeactual rows 等資訊。

ANALYZE 不是只有「多顯示一點資料」

這裡有一個很重要的差別:

EXPLAIN ANALYZE 真的會執行 Query。

如果今天只是:

SELECT ...

通常就是把查詢跑一次。

但如果寫的是:

EXPLAIN ANALYZE
DELETE FROM orders
WHERE ...;

那筆 DELETE 真的會發生。

INSERTUPDATE 也一樣。

所以在會修改資料的 Query 上使用 EXPLAIN ANALYZE 時,不能把它當成單純的預覽工具。若只想觀察執行結果、不保留修改,可以放在 Transaction 裡執行後再 ROLLBACK


同一支 SQL,Database 不一定只有一種跑法

知道 EXPLAIN ANALYZE 能看到執行方式後,下一個要先認識的是 Query Planner(查詢規劃器)

假設我寫:

SELECT *
FROM orders
WHERE user_id = 123;

對我們來說,SQL 只寫了一句:

找出 user_id = 123 的資料。

但 Database 還要決定:

要怎麼找?

它可能:

直接掃 Table

也可能:

先走 Index
再去拿需要的 Row

甚至還有其他方式。

PostgreSQL 的 Planner 會根據 Query、資料統計與成本估算,選一份它認為適合的 Execution Plan(執行計畫)

所以可以先把整個關係想成:

SQL
↓
Query Planner
↓
選擇 Execution Plan
↓
Database 實際執行

EXPLAIN ANALYZE 做的,就是讓我們看到這條路最後怎麼走。


第一眼先看:它到底怎麼找資料?

打開一份 Execution Plan,最先可以注意的是:

Database 選了哪一種 Scan?

PostgreSQL 裡常會看到:

Seq Scan
Index Scan
Bitmap Index Scan + Bitmap Heap Scan

Seq Scan:直接把 Table 掃過去

假設昨天那張 orders 有 100 萬筆資料,其中 90 萬筆都是:

status = completed

現在查:

SELECT *
FROM orders
WHERE status = 'completed';

雖然 status 有可能存在 Index,但 Query 本來就要拿出 90 萬筆。

這時 Planner 可能判斷:

都要讀這麼多資料了,直接掃 Table 比一直透過 Index 找還划算。

Execution Plan 就可能看到:

Seq Scan on orders

Seq ScanSequential Scan(循序掃描),可以先理解成:

直接依序掃過 Table,找出符合條件的資料。

看到 Seq Scan,不代表 Query 一定寫壞了。

像這個例子,status = 'completed' 篩完還剩下 90% 的資料,Planner 直接掃 Table 反而可能比較划算。


Index Scan:真的走了我們建立的 Index

再換回:

SELECT *
FROM orders
WHERE user_id = 123;

假設 user_id 有很多不同值,而 123 最後只會找到幾十筆。

如果已經建立:

CREATE INDEX idx_orders_user_id
ON orders(user_id);

Execution Plan 就可能看到:

Index Scan using idx_orders_user_id on orders

這時就很好讀了:

Index Scan
→ 這次選擇走 Index

using idx_orders_user_id
→ 使用的是這份 Index

如果下面還看到:

Index Cond: (user_id = 123)

就是在告訴我們:

這個條件被拿來使用 Index 找資料。

所以昨天問的:

「我建立的 Index 到底有沒有真的派上用場?」

在這裡就可以開始找到答案。


Bitmap:介在兩者中間的另一條路

那如果符合條件的資料:

  • 沒少到適合一筆一筆走 Index
  • 但也沒多到值得掃完整張 Table

呢?

這時有可能看到:

Bitmap Index Scan
↓
Bitmap Heap Scan

可以先用很白話的方式理解:

Bitmap Index Scan
→ 先透過 Index 整理出「哪些位置有我要的資料」

Bitmap Heap Scan
→ 再回 Table 把那些資料取出來

所以三種方式可以先這樣看:

Seq Scan
→ 直接掃 Table

Index Scan
→ 用 Index 找少量資料

Bitmap
→ 先整理一批位置,再回 Table 取資料

實際走哪一條,還是由 Planner 根據 Query 和資料狀況決定。


cost 看起來像時間,但它不是毫秒

知道 Database 選了哪條路後,再往右看一點。

Execution Plan 很常出現:

cost=0.29..8.31

第一次看到很容易把它讀成:

0.29 ms 到 8.31 ms?

cost 不是實際執行時間。

它可以先理解成:

Planner 預估「走這條 Execution Plan 要花多少工作量」的相對成本。

讀資料、處理 Row、做運算,都可能被算進這份估算裡,Planner 再拿不同 Plan 的 cost 互相比較。

cost=0.29..8.31
     ↑       ↑
 startup   total
  cost      cost

startup cost 可以先理解成:

產出第一筆 Row 前的預估成本。

total cost 則是:

假設這個 Node 全部跑完的預估總成本。

所以 8.31 不是 8.31 ms。

cost 看得是 Planner 的估算;真正執行後量到的時間,會出現在 actual time


actual time 才是實際執行後量到的時間

使用 EXPLAIN ANALYZE 後,會多看到:

actual time=0.020..0.080

這裡的時間才是實際執行量到的時間,而且單位是毫秒。

可以先這樣讀:

actual time=0.020..0.080
            ↑        ↑
         第一筆    完成這個 Node

所以:

cost
→ Planner 原本怎麼估

actual time
→ 實際跑完後量到多少

這兩個不能混在一起看。


rows:Planner 猜了幾筆,實際又有幾筆?

除了時間,我覺得 EXPLAIN ANALYZE 很有意思的地方,是可以直接把:

Database 原本以為會有多少資料

和:

實際真的有多少資料

放在一起看。

例如:

cost=0.29..8.31 rows=10

這裡的:

rows=10

是 Planner 的 Estimated Rows(預估資料筆數)

意思是:

我估計這個 Plan Node 大概會產生 10 筆資料。

使用 EXPLAIN ANALYZE 後,又可能看到:

actual time=0.020..0.080 rows=12

這個:

rows=12

才是實際執行後真的產生 12 筆。

如果變成:

estimated rows = 10
actual rows    = 5000

落差就很明顯了。

Planner 原本只估計會有 10 筆,實際卻有 5000 筆,代表它對這次 Query 會產生多少資料的估計差很多。

這時就值得繼續往資料統計、資料分布等方向檢查,而不是只盯著「有沒有 Index」。


把一份 EXPLAIN ANALYZE 從頭讀一次

前面的角色都認識之後了,讓我們一起來複習一下今天講的內容吧!

先回到昨天那支 Query:

EXPLAIN ANALYZE
SELECT *
FROM orders
WHERE user_id = 123
  AND status = 'completed'
ORDER BY created_at DESC;

這裡是為了教學簡化過的結果:

Index Scan using idx_orders_user_status on orders
  (cost=0.29..8.31 rows=10 width=120)
  (actual time=0.020..0.080 rows=12 loops=1)
  Index Cond: (
    user_id = 123
    AND status = 'completed'
  )

Planning Time: 0.150 ms
Execution Time: 0.100 ms

第一次看到這一大串,其實不用每個數字都讀。

先抓今天學的幾個地方。

① Database 走哪條路?

Index Scan

代表這次是透過 Index 查找。

而:

using idx_orders_user_status

告訴我們,它使用的正是昨天建立的:

idx_orders_user_status

所以至少可以確認:

這次 Execution Plan 真的有使用這份 Index。

② Planner 原本怎麼估?

cost=0.29..8.31
rows=10

代表 Planner 估計:

  • 這份 Plan 的總成本大約到 8.31
  • 最後大約會產生 10 筆 Row

記得,8.31 不是 8.31 ms。

③ 實際跑起來呢?

actual time=0.020..0.080
rows=12

實際執行後:

  • 這個 Node 的實際時間落在這個範圍
  • 實際產生 12

這次:

estimated = 10
actual    = 12

兩邊很接近。

④ 到底總共跑多久?

最後:

Execution Time: 0.100 ms

才是這一次 Query Execution 的整體執行時間。

所以拿到一份基本的 EXPLAIN ANALYZE,可以先照這個順序看:

走哪種 Plan?
↓
有沒有走想看的 Index?
↓
Planner 原本估多少?
↓
實際 rows 差多少?
↓
actual time / Execution Time 是多少?

先能回答這五件事,就已經可以開始用它檢查自己的 Query。


頁面開始變慢,只跟 AI 說「幫我優化」夠嗎?

Vibe Coding 很容易在功能做完、資料還不多的時候,看起來一切都很正常。

直到資料慢慢增加,某支 API 或某個頁面開始變慢,才發現:

「怎麼現在要等這麼久?」

這時最直覺的做法可能就是把 Query 丟給 AI:

「這支查詢很慢,幫我優化。」

AI 可以幫忙改 SQL、補 Index,甚至直接產出修改好的 Code。

但如果自己不知道怎麼查,真正缺掉的是中間這一段:

到底是哪支 Query 慢?
↓
Database 現在怎麼執行?
↓
慢在掃太多資料、沒走 Index,
還是 Planner 的估算和實際差很多?
↓
修改之後真的改善了嗎?
  • Vibe Coding:容易從「發現變慢」直接跳到「請 AI 幫我改」,中間的原因和驗證都交給 AI 判斷。
  • 專業開發:先找到慢的 Query,用 EXPLAIN ANALYZE 看原本的 Execution Plan,再針對真正的原因修改,最後重新執行一次比較前後結果。

AI 很適合幫忙提出優化方案。

但要知道該改什麼,得先知道 Database 現在到底慢在哪裡。


原來效能真的可以直接看

Index 讓我看到 Database Design 本身就會影響資料查找的效率。

EXPLAIN ANALYZE 更讓我覺得很神奇的是:

原來在 Database 裡,就有方法可以直接看到 Query 怎麼執行、實際花多少時間,甚至比較原本的估算和真正跑出來的結果。

這樣 Index 加完之後,也不只是看 Code 能不能跑,或憑感覺覺得好像變快了。

還可以真的把執行結果打開來看:

它到底有沒有幫上忙。

一路寫到這裡,這趟從 0→1 的工程世界也真的快走到終點了。

下一篇,就是這 30 天的最後一篇。

最後,就一起來回頭看看我們這一路都學了些什麼吧~


上一篇
Day28|查得到就夠了嗎?從 Index Design 看懂 Query 怎麼決定索引怎麼加
下一篇
Day 30|走完 30 天,再回頭看看第一天的地圖
系列文
夢幻甜品師闖工程世界:Vibe Coding vs 專業開發的 0→1 冒險攻略30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言